eletrotupi / tcc/ frontend/app/entry/[id].tsx11192d365abd4e4bf1cac55085657d3ce9c8262b
8.3 KB | 284 lines Raw
1
import { useState, useEffect } from 'react';
2
import { StyleSheet, Text, View, ScrollView, Pressable, ActivityIndicator } from 'react-native';
3
import { useLocalSearchParams, router, Stack } from 'expo-router';
4
import { format } from 'date-fns';
5
import { ptBR } from 'date-fns/locale';
6

7
import { Card, SubtleInfoCard } from '@/components/ui/Cards';
8
import { Badge } from '@/components/ui/Badge';
9
import { Button } from '@/components/ui/Button';
10
import { Col, Between, Row } from '@/components/ui/LayoutHelpers';
11
import { Section, SectionHeader } from '@/components/ui/Sections';
12
import { Theme, Colors, Spacing } from '@/constants/theme';
13
import { getMood } from '@/constants/moods';
14
import { getMoodComponent, intensityLabel, intensityToValue } from '@/constants/mood-components';
15
import { useMoodEntry, useDeleteMoodEntry } from '@/hooks';
16
import { getTimeBadge, formatMoment } from '@/lib/utils/time';
17
import { MoodComponentCard } from '@/components/ui/MoodComponentCard';
18

19
export default function MoodDetailScreen() {
20
  const { id } = useLocalSearchParams<{ id: string }>();
21
  const { data: entry, isLoading, isError } = useMoodEntry(id);
22
  const deleteMoodEntry = useDeleteMoodEntry();
23
  const [minutesLeft, setMinutesLeft] = useState(0);
24
  const [canDelete, setCanDelete] = useState(false);
25

26
  useEffect(() => {
27
    const updateTimer = () => {
28
      const moment = new Date(entry?.moment);
29
      const createdAt = new Date(entry?.moment);
30
      const msSinceCreation = Date.now() - createdAt.getTime();
31
      const deletionWindowMs = 5 * 60 * 1000;
32

33
      const newCanDelete = msSinceCreation < deletionWindowMs;
34
      const newMinutesLeft = Math.max(0, Math.ceil((deletionWindowMs - msSinceCreation) / 60000));
35

36
      setCanDelete(newCanDelete);
37
      setMinutesLeft(newMinutesLeft);
38
    };
39

40
    updateTimer(); // Set initial values
41

42
    const interval = setInterval(updateTimer, 1000);
43

44
    return () => clearInterval(interval);
45
  }, [entry?.moment]);
46

47
  if (isLoading) {
48
    return (
49
      <View style={styles.centered}>
50
        <ActivityIndicator size="large" color={Theme.colors.tint} />
51
      </View>
52
    );
53
  }
54

55
  if (isError || !entry) {
56
    return (
57
      <View style={styles.centered}>
58
        <Text style={styles.errorText}>Registro não encontrado.</Text>
59
        <Pressable onPress={() => router.back()} style={styles.backButton}>
60
          <Text style={styles.backButtonText}>Voltar</Text>
61
        </Pressable>
62
      </View>
63
    );
64
  }
65

66
  const moment = new Date(entry.moment);
67
  const moodDef = getMood(entry.selectedMood.toLowerCase());
68
  const badge = getTimeBadge(moment);
69

70
  const handleDelete = async () => {
71
    await deleteMoodEntry.mutateAsync(String(entry.id));
72

73
    router.back();
74
  };
75

76
  const deleteButtonMsg = () => {
77
    const msg = "Excluir Registro";
78
    const timeLeftStr = `(Disponível por ${minutesLeft < 1 ? '<1' : minutesLeft}:00)`;
79
    const suffix = canDelete ? timeLeftStr : "(Expirado)"
80

81
    return `${msg} ${suffix}`
82
  }
83

84
  return (
85
    <>
86
      <Stack.Screen options={{ title: 'Ver detalhes' }} />
87

88
      <ScrollView
89
        style={styles.container}
90
        contentContainerStyle={styles.content}
91
        showsVerticalScrollIndicator={false}
92
      >
93
        {/* Header card */}
94
        <Card style={styles.headerCard}>
95
          <Between>
96
            <Row gap={12} style={{ alignItems: 'center' }}>
97
              <Text style={styles.moodIcon}>{moodDef?.icon ?? '😐'}</Text>
98
              <Col gap={2}>
99
                <Text style={styles.moodLabel}>{moodDef?.label ?? entry.selectedMood}</Text>
100
                <Text style={styles.moodTime}>{formatMoment(moment)}</Text>
101
              </Col>
102
            </Row>
103
            <Badge
104
              label="Registro Ativo"
105
              backgroundColor={Theme.colors.activeBackground}
106
              color={Theme.colors.tint}
107
            />
108
          </Between>
109
        </Card>
110

111
        {/* Core levels */}
112
        <Section>
113
          <SectionHeader title="Níveis" variant="subtle" />
114
            {[
115
              { id: 'energy', label: 'Energia',  value: entry.energyLevel  },
116
              { id: 'stress', label: 'Estresse', value: entry.stressLevel  },
117
              { id: 'anxiety', label: 'Ansiedade', value: entry.anxietyLevel },
118
            ].map((item, index, arr) => (
119
              <Card key={item.id} style={{ padding: Spacing.cardGap }}>
120
                <View key={item.label}>
121
                  <View style={styles.componentRow}>
122
                    <Text style={styles.componentLabel}>{item.label}</Text>
123
                    <Text style={styles.componentIntensityGreen}>{item.value}/10</Text>
124
                  </View>
125

126
                  <View style={styles.progressTrack}>
127
                    <View style={[styles.progressFill, { width: `${(item.value / 10) * 100}%` }]} />
128
                  </View>
129
                </View>
130
              </Card>
131
            ))}
132
        </Section>
133

134
        <Section>
135
          <SectionHeader title="Componentes" variant="subtle" />
136
          <Col gap={0}>
137
            {entry.moodComponents.map((comp, index) => {
138
              const def = getMoodComponent(comp.component.toLowerCase());
139
              const numericIntensity = comp.intensity === 'LIGHT' ? 2 : comp.intensity === 'MODERATE' ? 5 : 8;
140

141
              return (
142
                <MoodComponentCard
143
                  key={comp.id}
144
                  id={comp.component.toLowerCase()}
145
                  intensity={comp.intensity}
146
                />
147
              );
148
            })}
149
          </Col>
150
        </Section>
151

152
        {/* Annotation / notes */}
153
        {entry.annotation && (
154
          <Section>
155
            <SectionHeader title="Notas" variant="subtle" />
156
            <Card style={{ padding: Spacing.cardGap }}>
157
              <Text style={styles.annotation}>"{entry.annotation?.trim()}"</Text>
158
            </Card>
159
          </Section>
160
        )}
161

162
        {/* Delete */}
163
        <View style={styles.section}>
164
          <Text style={styles.sectionLabel}>GESTÃO DO REGISTRO</Text>
165
          <Button variant="danger"
166
            isLoading={deleteMoodEntry.isPending}
167
            disabled={!canDelete || deleteMoodEntry.isPending}
168
            onPress={handleDelete}
169
            title={deleteButtonMsg()} />
170

171
          <SubtleInfoCard style={styles.deleteInfo}
172
            text={`
173
              A exclusão de registros é permitida apenas nos primeiros 5 minutos
174
              após o envio para garantir a integridade do histórico emocional.
175
              `} />
176
        </View>
177

178
      </ScrollView>
179
    </>
180
  );
181
}
182

183
const styles = StyleSheet.create({
184
  container: {
185
    flex: 1,
186
    backgroundColor: Theme.colors.background,
187
  },
188
  content: {
189
    padding: Spacing.containerPadding,
190
    gap: Spacing.sectionGap,
191
    paddingBottom: 48,
192
  },
193
  centered: {
194
    flex: 1,
195
    justifyContent: 'center',
196
    alignItems: 'center',
197
    gap: 12,
198
  },
199
  errorText: {
200
    fontSize: 15,
201
    color: Colors.light.textSecondary,
202
  },
203
  backButton: {
204
    paddingHorizontal: 20,
205
    paddingVertical: 10,
206
    backgroundColor: Theme.colors.tint,
207
    borderRadius: Theme.borderRadius.md,
208
  },
209
  backButtonText: {
210
    fontWeight: '700',
211
    color: Theme.colors.text,
212
  },
213

214
  // Header
215
  headerCard: {
216
    padding: 16,
217
  },
218
  moodIcon: {
219
    fontSize: 40,
220
  },
221
  moodLabel: {
222
    fontSize: 17,
223
    fontWeight: '700',
224
    color: Colors.light.text,
225
  },
226
  moodTime: {
227
    fontSize: 13,
228
    color: Colors.light.textSecondary,
229
  },
230

231
  // Sections
232
  section: {
233
    gap: 8,
234
  },
235
  sectionLabel: {
236
    fontSize: 11,
237
    fontWeight: '600',
238
    letterSpacing: 0.8,
239
    color: Colors.light.textSecondary,
240
    textTransform: 'uppercase',
241
  },
242

243
  // Components
244
  componentRow: {
245
    flexDirection: 'row',
246
    justifyContent: 'space-between',
247
    alignItems: 'center',
248
    paddingVertical: 10,
249
  },
250
  componentLabel: {
251
    fontSize: 15,
252
    fontWeight: '700',
253
    color: Colors.light.text,
254
  },
255
  componentIntensity: {
256
    fontSize: 13,
257
    color: Colors.light.textSecondary,
258
  },
259
  componentIntensityGreen: {
260
    fontSize: 13,
261
    fontWeight: '700',
262
    color: Theme.colors.tint,
263
  },
264
  progressTrack: {
265
    height: 6,
266
    backgroundColor: Theme.colors.light.divider,
267
    borderRadius: Theme.borderRadius.full,
268
    marginBottom: 4,
269
    overflow: 'hidden',
270
  },
271
  progressFill: {
272
    height: '100%',
273
    backgroundColor: Theme.colors.light.tint,
274
    borderRadius: Theme.borderRadius.full,
275
  },
276
  // Annotation
277
  annotation: {
278
    fontSize: 14,
279
    lineHeight: 22,
280
    color: Colors.light.textSecondary,
281
    fontStyle: 'italic',
282
    padding: 4,
283
  },
284
});